package com.xqb.listen.manager.media.impl

import android.content.ContentResolver
import android.content.Context
import android.database.Cursor
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.core.database.getLongOrNull
import androidx.core.database.getStringOrNull
import com.xqb.listen.manager.media.utils.FinderUtil
import com.xqb.listen.manager.media.LocalMediaFinder
import com.xqb.listen.manager.media.model.AlbumModel
import com.xqb.listen.manager.media.model.AlbumType
import com.xqb.listen.manager.media.model.MediaModel
import com.xqb.listen.manager.media.model.PageModel
import com.xqb.listen.manager.permission.PermissionUtil
import com.xqb.listen.manager.permission.model.MediaType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlin.math.ceil

/**
 * @author Ling
 * @date :2024/11/26 16:54
 */
class LocalMediaFinderImp private constructor() : LocalMediaFinder {

    private var PAGE_COUNT = 30
    private var ENABLE_ALL = true

    companion object{

        private var localMediaFinder : LocalMediaFinder? = null

        @Synchronized
        @JvmStatic
        fun getSingleInstance() : LocalMediaFinder {
            if(localMediaFinder == null){
                localMediaFinder = LocalMediaFinderImp()
            }
            return localMediaFinder!!
        }
    }

    override fun setPageCount(count: Int) : LocalMediaFinder {
        if(count <= 0)
            this.PAGE_COUNT = 30
        else
            this.PAGE_COUNT = count

        return localMediaFinder!!
    }

    override fun enableAllAlbum(enable: Boolean): LocalMediaFinder {
        this.ENABLE_ALL = enable
        return localMediaFinder!!
    }

    override suspend fun getAlbum(context: Context, fillAlbumItem : Boolean, albumType: AlbumType): List<AlbumModel>? {
        if(albumType  == AlbumType.AUDIO){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.AUDIO)) return null
        }else if(albumType == AlbumType.VIDEO){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.VIDEO)) return null
        }else if(albumType == AlbumType.IMAGE){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.IMAGE)) return null
        }else{
            if(!PermissionUtil.hasMediaPermission(context , MediaType.VIDEO) || !PermissionUtil.hasMediaPermission(context , MediaType.IMAGE)) return null
        }

        val data = withContext(Dispatchers.IO){
            val cursor = try{
                getAlbumCursor(context , albumType)
            }catch (e : Exception){
                null
            }

            if(cursor == null || cursor.count <= 0){
                cursor?.close()
                return@withContext null
            }else{
                val localAlbumList = arrayListOf<AlbumModel>()
                var totalCount = 0
                try {
                    if(FinderUtil.isBuildQ()){
                        val countMap = hashMapOf<Long , Long>()
                        val hashSet = hashSetOf<Long>()
                        while (cursor.moveToNext()) {
                            try {
                                val bucketId: Long = cursor.getLong(cursor.getColumnIndexOrThrow("bucket_id"))
                                var newCount = countMap[bucketId]
                                if (newCount == null) {
                                    newCount = 1L
                                } else {
                                    newCount++
                                }
                                countMap[bucketId] = newCount

                                if (hashSet.contains(bucketId)) {
                                    continue
                                }

                                if (!countMap.containsKey(bucketId)) {
                                    continue
                                }

                                val bucketDisplayName = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("bucket_display_name")) ?: "default"
                                val mimeType: String = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
                                val url: String = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA))

                                val mediaAlbum = AlbumModel(
                                    bucketId,
                                    bucketDisplayName,
                                    url,
                                    mimeType,
                                    countMap[bucketId]?.toInt() ?: 0
                                )

                                localAlbumList.add(mediaAlbum)
                                hashSet.add(bucketId)
                            }catch (e : Exception){
                                e.printStackTrace()
                                Log.e("--->", "getAlbum-Q: ${e.message}")
                            }
                        }

                        for (albumItem in localAlbumList) {
                            val size = countMap[albumItem.albumId]?.toInt() ?: 0
                            albumItem.albumTotalCount = size
                            totalCount += size
                        }
                    }else{
                        while (cursor.moveToNext()) {
                            try{
                                val url: String = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA))
                                val bucketDisplayName = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("bucket_display_name")) ?: "default"
                                val mimeType: String = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
                                val bucketId: Long = cursor.getLong(cursor.getColumnIndexOrThrow("bucket_id"))
                                val size: Int = cursor.getInt(cursor.getColumnIndexOrThrow("count"))

                                val mediaAlbum = AlbumModel(
                                    bucketId,
                                    bucketDisplayName,
                                    url,
                                    mimeType,
                                    size
                                )
                                localAlbumList.add(mediaAlbum)
                                totalCount += size
                            }catch (e : Exception){
                                e.printStackTrace()
                                Log.e("---->", "getAlbum-R: ${e.message}")
                            }
                        }
                    }

                    var firstUrl : String? = null
                    var firstMimeType : String? = null
                    if (cursor.moveToFirst()) {
                        firstUrl = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA))
                        firstMimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
                    }


                    localAlbumList.sortBy { it.albumName.lowercase() }

                    if(fillAlbumItem){
                        val allAlbumItems = arrayListOf<MediaModel>()

                        for(albumItem in localAlbumList){
                            albumItem.albumItems = getMedia(context , albumType , albumItem.albumId)
                            albumItem.albumItems?.let {
                                allAlbumItems.addAll(it)
                            }
                        }

                        if(ENABLE_ALL){
                            val mediaAlbum = AlbumModel(
                                -1,
                                "All",
                                firstUrl?:"",
                                firstMimeType?:"",
                                totalCount,
                                allAlbumItems
                            )

                            localAlbumList.add(0 , mediaAlbum)
                        }
                    }else{
                        if(ENABLE_ALL){
                            val mediaAlbum = AlbumModel(
                                -1,
                                "All",
                                firstUrl?:"",
                                firstMimeType?:"",
                                totalCount
                            )
                            localAlbumList.add(0 , mediaAlbum)
                        }
                    }
                }catch (e : Exception){
                    Log.e("--->", "getAlbum: ${e.message}")
                    return@withContext null
                }finally {
                    cursor.close()
                }
                return@withContext localAlbumList
            }
        }
        return data
    }

    override suspend fun getMedia(context: Context, albumType: AlbumType, albumId : Long?): List<MediaModel>? {
        if(albumType  == AlbumType.AUDIO){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.AUDIO)) return null
        }else if(albumType == AlbumType.VIDEO){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.VIDEO)) return null
        }else if(albumType == AlbumType.IMAGE){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.IMAGE)) return null
        }else{
            if(!PermissionUtil.hasMediaPermission(context , MediaType.VIDEO) || !PermissionUtil.hasMediaPermission(context , MediaType.IMAGE)) return null
        }

        val data = withContext(Dispatchers.IO){
            val cursor = try{
                getMediaCursor(context, albumType , albumId , null)
            }catch (e : Exception){
                null
            }
            try{
                if(cursor == null || cursor.count <= 0){
                    cursor?.close()
                    return@withContext null
                }else{
                    val mediaList = arrayListOf<MediaModel>()
                    while (cursor.moveToNext() && isActive) {
                        try {
                            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA))
                            val mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
                            val duration = cursor.getLongOrNull(cursor.getColumnIndexOrThrow("duration")) ?: 0
                            val width = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.WIDTH))
                            val height = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.HEIGHT))
                            var name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME))
                            val author = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.ARTIST))
                            if((mimeType.startsWith("video" , true) || mimeType.startsWith("audio" , true)) && duration <= 0) continue
                            if(name.isNullOrEmpty()){
                                name = FinderUtil.getNameFromFilepath(path)
                            }
                            mediaList.add(MediaModel(name , path , mimeType , width , height , author , duration))
                        } catch (e: Exception) {
                            e.printStackTrace()
                            Log.e("--->", "getMedia error ${e.message}")
                        }
                    }
                    return@withContext mediaList
                }
            }catch (e : Exception){
                return@withContext null
            }finally {
                cursor?.close()
            }
        }

        return data
    }

    override suspend fun getMediaByPage(context: Context, pageIndex: Int, albumType: AlbumType, albumId : Long?): PageModel? {
        if(pageIndex < 0) return null
        if(albumType  == AlbumType.AUDIO){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.AUDIO)) return null
        }else if(albumType == AlbumType.VIDEO){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.VIDEO)) return null
        }else if(albumType == AlbumType.IMAGE){
            if(!PermissionUtil.hasMediaPermission(context , MediaType.IMAGE)) return null
        }else{
            if(!PermissionUtil.hasMediaPermission(context , MediaType.VIDEO) || !PermissionUtil.hasMediaPermission(context , MediaType.IMAGE)) return null
        }

        val data = withContext(Dispatchers.IO){
            val cursor = try{
                getMediaCursor(context, albumType , albumId , pageIndex)
            }catch (e : Exception){
                null
            }
            try{
                if(cursor == null || cursor.count <= 0){
                    cursor?.close()
                    return@withContext null
                }else{
                    val mediaList = arrayListOf<MediaModel>()
                    while (cursor.moveToNext() && isActive) {
                        try {
                            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA))
                            val mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
                            val duration = cursor.getLongOrNull(cursor.getColumnIndexOrThrow("duration")) ?: 0
                            val width = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.WIDTH))
                            val height = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.HEIGHT))
                            var name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME))
                            val author = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.ARTIST))
                            if((mimeType.startsWith("video" , true) || mimeType.startsWith("audio" , true)) && duration <= 0) continue
                            if(name.isNullOrEmpty()){
                                name = FinderUtil.getNameFromFilepath(path)
                            }
                            mediaList.add(MediaModel(name , path , mimeType , width , height , author , duration))
                        } catch (e: Exception) {
                            e.printStackTrace()
                            Log.e("--->", "getMediaByPage error ${e.message}")
                        }
                    }
                    val totalCountCursor = getMediaCursor(context , albumType , albumId , null)
                    val totalCount = totalCountCursor?.count ?: 0
                    return@withContext PageModel(pageIndex , PAGE_COUNT , totalCount , mediaList , ceil(totalCount.toFloat() / PAGE_COUNT).toInt() != pageIndex)
                }
            }catch (e : Exception){
                return@withContext null
            }finally {
                cursor?.close()
            }
        }

        return data
    }

    private fun getMediaCursor(context: Context, albumType : AlbumType, albumId: Long?, pageIndex: Int?) : Cursor?{
        if(pageIndex == null){
            return context.contentResolver.query(
                MediaStore.Files.getContentUri("external"),
                FinderUtil.MEDIA_PROJECTION,
                getMediaSelection(albumType , albumId),
                getSelectionArgs(albumType , albumId),
                FinderUtil.SORT_DESC
            )
        }else{
            if(FinderUtil.isBuildR()){
                val bundle = Bundle()
                bundle.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, getMediaSelection(albumType , albumId))
                bundle.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, getSelectionArgs(albumType , albumId))
                bundle.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, FinderUtil.SORT_DESC)
                bundle.putInt(ContentResolver.QUERY_ARG_LIMIT , PAGE_COUNT)
                bundle.putInt(ContentResolver.QUERY_ARG_OFFSET , (pageIndex - 1) * PAGE_COUNT)

                return context.contentResolver.query(
                    MediaStore.Files.getContentUri("external"),
                    FinderUtil.MEDIA_PROJECTION,
                    bundle,
                    null
                )
            }else{
                return context.contentResolver.query(
                    MediaStore.Files.getContentUri("external"),
                    FinderUtil.MEDIA_PROJECTION,
                    getMediaSelection(albumType , albumId),
                    getSelectionArgs(albumType , albumId),
                    "${FinderUtil.SORT_DESC} limit $PAGE_COUNT offset ${(pageIndex - 1) * PAGE_COUNT}"
                )
            }
        }
    }

    private fun getAlbumCursor(context: Context, albumType : AlbumType) : Cursor?{
        return context.contentResolver.query(
            MediaStore.Files.getContentUri("external"),
            FinderUtil.getAlbumProjection(),
            getAlbumSelection(albumType),
            getSelectionArgs(albumType , null),
            FinderUtil.SORT_DESC
        )
    }

    private fun getMediaSelection(albumType: AlbumType, albumId: Long?): String {
        val stringBuilder = java.lang.StringBuilder()
        when (albumType) {
            AlbumType.VIDEO, AlbumType.AUDIO -> {
                stringBuilder
                    .append("(")
                    .append(MediaStore.Files.FileColumns.MEDIA_TYPE)
                    .append("=?")
                    .append(" AND duration >= 1000")
                    .append(" AND ")
                    .append(MediaStore.Files.FileColumns.SIZE + "> 0)")
                return if(albumId == null)
                    stringBuilder.toString()
                else
                    stringBuilder
                        .append(" AND bucket_id = ?")
                        .toString()
            }
            AlbumType.IMAGE -> {
                stringBuilder
                    .append("(")
                    .append(MediaStore.Files.FileColumns.MEDIA_TYPE)
                    .append("=?")
                    .append(" AND ")
                    .append(MediaStore.Files.FileColumns.SIZE + "> 0)")

                return if(albumId == null)
                    stringBuilder.toString()
                else
                    stringBuilder
                        .append(" AND bucket_id = ?")
                        .toString()
            }
            else -> {
                stringBuilder
                    .append("(")
                    .append(MediaStore.Files.FileColumns.MEDIA_TYPE)
                    .append("=?")
                    .append(" OR ")
                    .append(MediaStore.Files.FileColumns.MEDIA_TYPE)
                    .append("=?")
                    .append(" AND duration >= 1000")
                    .append(" AND ")
                    .append(MediaStore.Files.FileColumns.SIZE + "> 0)")
                return if(albumId == null)
                    stringBuilder.toString()
                else
                    stringBuilder
                        .append(" AND bucket_id = ?")
                        .toString()
            }
        }
    }

    private fun getAlbumSelection(albumType: AlbumType) : String{
        val stringBuilder = java.lang.StringBuilder()
        when (albumType) {
            AlbumType.VIDEO, AlbumType.AUDIO -> {
                stringBuilder
                    .append("(")
                    .append(MediaStore.Files.FileColumns.MEDIA_TYPE)
                    .append("=?")
                    .append(" AND duration >= 1000")
                    .append(" AND ")
                    .append(MediaStore.Files.FileColumns.SIZE + "> 0")
                    .append(")")
                return if (FinderUtil.isBuildQ()) {
                    stringBuilder.toString()
                } else {
                    stringBuilder
                        .append(")")
                        .append(" GROUP BY (bucket_id")
                        .toString()
                }
            }
            AlbumType.IMAGE -> {
                stringBuilder
                    .append("(")
                    .append(MediaStore.Files.FileColumns.MEDIA_TYPE)
                    .append("=?")
                    .append(" AND ")
                    .append(MediaStore.Files.FileColumns.SIZE + "> 0")
                    .append(")")
                return if (FinderUtil.isBuildQ()) {
                    stringBuilder.toString()
                } else {
                    stringBuilder
                        .append(")")
                        .append(" GROUP BY (bucket_id")
                        .toString()
                }
            }
            else -> {
                stringBuilder
                    .append("(")
                    .append(MediaStore.Files.FileColumns.MEDIA_TYPE)
                    .append("=?")
                    .append(" OR ")
                    .append(MediaStore.Files.FileColumns.MEDIA_TYPE)
                    .append("=?")
                    .append(" AND duration >= 1000")
                    .append(" AND ")
                    .append(MediaStore.Files.FileColumns.SIZE + "> 0")
                    .append(")")
                return if (FinderUtil.isBuildQ()) {
                    stringBuilder.toString()
                } else {
                    stringBuilder
                        .append(")")
                        .append(" GROUP BY (bucket_id")
                        .toString()
                }
            }
        }
    }

    private fun getSelectionArgs(albumType: AlbumType, albumId: Long?) : Array<String>{
        return when(albumType){
            AlbumType.VIDEO-> {
                if(albumId == null)
                    arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString())
                else
                    arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString() , albumId.toString())
            }
            AlbumType.IMAGE-> {
                if(albumId == null)
                    arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString())
                else
                    arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString() , albumId.toString())
            }
            AlbumType.AUDIO-> {
                if(albumId == null)
                    arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO.toString())
                else
                    arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO.toString() , albumId.toString())
            }
            else-> {
                if(albumId == null)
                    arrayOf(
                        MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
                        MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()
                    )
                else
                    arrayOf(
                        MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
                        MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString(),
                        albumId.toString()
                    )
            }
        }
    }
}